import logging
import math
import numpy as np

from messenger.utils.consumer_requests_util import redis_client
from messenger.utils.redis_util import RedisKey
from messenger.schema.pmachine import PmachineCoef


class MachineWeightCalculate:
    def __init__(self, calculte_coef, w_solid):
        self._e_min = calculte_coef.e_min
        self._i_min = calculte_coef.i_min
        self._i_max = calculte_coef.i_max
        self._w_rest = calculte_coef.w_rest
        self._w_solid = w_solid

    @property
    def init_weight(self):
        return self._w_solid, 0, self._w_solid

    @property
    def init_attenuation_coef(self):
        coef_matrix = np.mat('{},1;{},1'.format(self._i_min, self._i_max))
        if (self._w_solid - self._e_min) - 1 == 0 or \
                self._w_rest - 1 == 0:
            raise ValueError("calculate coef is not properly")
        else:
            trans_matrix = np.mat(
                '{},{}'.format(
                    math.log(self._w_solid / (self._w_solid - self._e_min) - 1, math.e
                             ),
                    math.log(self._w_solid / self._w_rest - 1, math.e)
                )
            ).T
            result_coef = np.linalg.solve(coef_matrix, trans_matrix)
            return round(float(result_coef[0][0]), 2), round(float(result_coef[1][0]), 2)

    @staticmethod
    def refresh_weight_current(w_current, w_total):
        return w_current - w_total

    @staticmethod
    def _attenution_calculate(fail_num, alpha, beta, w_solid):
        if 1 + math.e ** (alpha * fail_num + beta) == 0:
            raise ValueError("attenution calculate denominator is Zero,it's wrong")
        return w_solid / (1 + math.e ** (alpha * fail_num + beta))

    def get_current_weight(self, pmachine_coef, w_current):
        if pmachine_coef.fail_num == 0:
            w_effective = pmachine_coef.w_solid

        elif 0 < pmachine_coef.fail_num < self._i_max:
            w_effective = self._attenution_calculate(
                pmachine_coef.fail_num,
                pmachine_coef.alpha,
                pmachine_coef.beta,
                pmachine_coef.w_solid
            )

        elif pmachine_coef.fail_num > self._i_max:
            w_effective = self._w_rest

        else:
            raise ValueError("fail_num can't be {}".format(pmachine_coef.fail_num))

        w_current = w_current + w_effective

        return w_current


class Pmachine(MachineWeightCalculate):
    def __init__(self, calculate_coef, w_solid=0):
        super().__init__(calculate_coef, w_solid)

    @property
    def init_machine_weight_info(self):
        machine_w_solid, machine_w_current, machine_effective = self.init_weight
        alpha, beta = self.init_attenuation_coef
        return alpha, beta, machine_w_current

    def machine_choice(self, pmachines):
        num = 0
        try:
            for pm in pmachines:

                pmachine_coef = PmachineCoef(
                    pm.get('fail_num'),
                    pm.get('alpha'),
                    pm.get('beta'),
                    pm.get('w_solid')
                )
                machine_group = pm.get("machine_group")
                machine_w_current = self.get_current_weight(
                    pmachine_coef,
                    float(
                        bytes.decode(
                            redis_client.consumer_get(
                                RedisKey.pmachine_ip(
                                    machine_group.get("id"),
                                    pm.get('ip')
                                )
                            )
                        )
                    )
                )

                if num == 0:
                    base_w = machine_w_current
                    choose_machine = pm
                    num += 1
                else:
                    num += 1
                    tmp_w = machine_w_current
                    if tmp_w > base_w:
                        base_w = tmp_w
                        choose_machine = pm
        except ValueError as e:
            logging.error(e)
        return choose_machine, base_w